/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package co.edu.unal.ahnmodeler.model;

import co.edu.unal.ahnmodeler.controller.NetworkController;
import co.edu.unal.ahnmodeler.main.App;
import co.edu.unal.ahnmodeler.util.RandomObjects;
import java.awt.Color;
import java.awt.Point;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Clase que representa un nodo en la red Ad-Hoc.
 * @author raider
 */
public class Node implements Runnable {

    private final int tick = 350;

    /**
     * Constructor de la clase. Inicializa aleatoriamente el color del nodo, el punto de inicio en el modelo y el rango de cobertura.
     */
    public Node() {
        this.color = RandomObjects.randomColor();
        this.startPoint = RandomObjects.randomPoint(App.getmFrame().getpModel().getWidth(),App.getmFrame().getpModel().getHeight());
        this.range = RandomObjects.randomRange();
        this.power=RandomObjects.randomNodeEnergy();
        this.minRange=range/2;
        netControl = App.getNetController();
        cont=0;
    }

    /**
     * Permite obtener el punto de inicio del nodo en el modelo.
     * @return Punto de inicio del nodo en el modelo.
     */
    public Point getStartPoint() {
        return startPoint;
    }

    /**
     * Permite obtener el color del nodo.
     * @return Color del nodo.
     */
    public Color getColor() {
        return color;
    }

    /**
     * Permite determinar si el nodo se encuentra seleccionado.
     * @return Si el nodo se encuentra seleccionado.
     */
    public boolean isSelected() {
        return selected;
    }

    /**
     * Permite asignar la selección del nodo de acuerdo al parámetro recibido.
     * @param selected Estado de la selección del nodo.
     */
    public void setSelected(boolean selected) {
        this.selected = selected;
    }

    /**
     * Permite determinar si el nodo se encuentra en movimiento e interactuando con los demás nodos en la red Ad-hoc.
     * @return Estado actual del nodo en la red Ad-hoc.
     */
    public boolean isRunning() {
        return running;
    }

    /**
     * Permite asignar el estado del nodo en la red Ad-hoc. Si el parámetro recibido es <i>True</i>
     * el nodo se encuentra en movimiento e interactuando con los demás nodos de la red Ad-hoc.
     * @param running Estado del nodo en la red Ad-hoc.
     */
    public void setRunning(boolean running) {
        this.running = running;
    }

    /**
     * Permite obtener el rango de cobertura del nodo.
     * @return Rango de cobertura del nodo en la red.
     */
    public int getRange() {
        return range;
    }

    /**
     * Permite al nodo desplazarse hacía una dirección con una velocidad constante durante un tiempo determinado.
     * Dirección:
     * 0: Sur
     * 1: SurEste
     * 2: Este
     * 3: NorEste
     * 4: Norte
     * 5: NorOeste
     * 6: Oeste
     * 7: SurOeste
     */
    public void move() {
        int distance = (int) Math.floor((tick / 1000.0) * speed);
        switch (direction) {
            case 0:
                if (!validateExitPoint(new Point(startPoint.x, startPoint.y + distance)))
                    startPoint = new Point(startPoint.x, startPoint.y + distance);
                break;
            case 1:
                if (!validateExitPoint(new Point((int) (startPoint.x + (distance * 0.7)), (int) (startPoint.y + (distance * 0.7)))))
                    startPoint = new Point((int) (startPoint.x + (distance * 0.7)), (int) (startPoint.y + (distance * 0.7)));
                break;
            case 2:
                if (!validateExitPoint(new Point(startPoint.x + distance, startPoint.y)))
                    startPoint = new Point(startPoint.x + distance, startPoint.y);
                break;
            case 3:
                if (!validateExitPoint(new Point((int) (startPoint.x + (distance * 0.7)), (int) (startPoint.y - (distance * 0.7)))))
                    startPoint = new Point((int) (startPoint.x + (distance * 0.7)), (int) (startPoint.y - (distance * 0.7)));
                break;
            case 4:
                if (!validateExitPoint(new Point(startPoint.x, startPoint.y - distance)))
                    startPoint = new Point(startPoint.x, startPoint.y - distance);
                break;
            case 5:
                if (!validateExitPoint(new Point((int) (startPoint.x - (distance * 0.7)), (int) (startPoint.y - (distance * 0.7)))))
                    startPoint = new Point((int) (startPoint.x - (distance * 0.7)), (int) (startPoint.y - (distance * 0.7)));
                break;
            case 6:
                if (!validateExitPoint(new Point(startPoint.x - distance, startPoint.y)))
                    startPoint = new Point(startPoint.x - distance, startPoint.y);
                break;
            case 7:
                if (!validateExitPoint(new Point((int) (startPoint.x - (distance * 0.7)), (int) (startPoint.y + (distance * 0.7)))))
                    startPoint = new Point((int) (startPoint.x - (distance * 0.7)), (int) (startPoint.y + (distance * 0.7)));
                break;
        }
//        if (validateExitPoint(startPoint)) {
//            netControl.deleteNode(this);
//        }
    }

    /**
     * Permite determianr si el punto que se recibe como parámetro ha salido de los límites del modelo.
     * @param sp Punto que será evaluado.
     * @return Si el punto ha salido de los límites del modelo.
     */
    public boolean validateExitPoint(Point sp) {
        Point p = new Point(sp.x, sp.y);
        if (p.x < 0 || p.y < 0 || p.x > App.getmFrame().getpModel().getWidth() || p.y > App.getmFrame().getpModel().getHeight()){
           direction = RandomObjects.randomDirection();
           return true; 
        }else{
            return false;
        }
    }

    /**
     * Permite obtener los nodos que se encuentran conectados entre sí.
     * @return Nodos que tienen conexiones entre sí.
     */
    public ArrayList<Node> getConnectedNodes(){
        ArrayList<Node> connectedNodes = new ArrayList<>();
        for (Connection con : App.getNetwork().getConnections()) {
            if(this.equals(con.getOrigin())){
                connectedNodes.add(con.getTarget());
            }
        }
        return connectedNodes;
    }
    
    
    /**
     * Permite obtener si el nodo se encuentra ocupado.
     * @return El estado del nodo si se encuentra ocupado.
     */
    public boolean isBusy(){
        return busy;
    }

    /**
     * Permite asignar el valor del estado de nodo que indica si está ocupado en el envío de paquetes.
     * @param busy Nuevo estado del nodo que determina si se encuentra enviando paquetes.
     */
    public void setBusy(boolean busy) {
        this.busy = busy;
    }

    /**
     * Permite determinar si el nodo se encuentra enviando un paquete.
     * @return Estado del nodo de acuerdo a si se encuentra enviando un paquete.
     */
    public boolean isSending() {
        return sending;
    }

    /**
     * Permite asignar si el nodo se encuentra enviando un paquete.
     * @param sending Nuevo estado que indica si el nodo se encuentra enviando un paquete.
     */
    public void setSending(boolean sending) {
        this.sending = sending;
    }

    /**
     * Permite obtener la ruta desde un nodo cualquiera en la red hasta este nodo.
     * @return Ruta desde otro nodo en la red hasta este nodo.
     */
    public ArrayList<Node> getRoute() {
        return route;
    }

    public void setRoute(ArrayList<Node> route) {
        this.route = route;
    }
    
    /**
     * Permite disminuir en una unidad el nivel de energía del nodo.
     */
    public void decreasePower() {
        power--;
        cont++;
        if(range>minRange && cont>99){
            range--;
            cont=0;
        }
    }
    
    /**
     * Permite obtener los nodos unidos por una conexión con este nodo.
     * @return Nodos con quien este nodo tiene conexión.
     */
    public ArrayList<Node> getNeighbors(){
        ArrayList<Node> neighbors=new ArrayList();
        for (Connection connection : netControl.getConnections()) {
            Node target = connection.getTarget();
            if (connection.getOrigin()==this && !target.isBusy()) {
                neighbors.add(target);
            }
        }
        return neighbors;
    }
    
    /**
     * Permite enviar un paquete hacia un nodo de destino.
     * @param route Ruta hasta el momento del paquete.
     * @param destination Nodo destino del paquete.
     */
    public void sendData(Node source,Node destination){
        route=(ArrayList<Node>) source.getRoute().clone();
        route.add(this);
        if (this==destination) {
            route.get(0).receiveRoute(route);
        }else{
            busy=true;
            ArrayList<Node> neighbors = getNeighbors();
            if (neighbors.size()>0) {
                for (Node neighbor : neighbors) {
                    power--;
                    neighbor.sendData(this,destination);
                }
            }
            busy=false;
        }
    }
    
    /**
     * Permite recibir la ruta de un paquete desde un nodo de inicio hasta un nodo de destino.
     * @param route Ruta de un paquete.
     */
    public void receiveRoute(ArrayList<Node> route){
        sending=false;
        this.route=route;
    }
    
    @Override
    public void run() {
        int t = 0;
        netControl.addConnetions();
        while (running) {
            if (power<=0) {
                netControl.deleteNode(this);
            }
            if (t <= 0) {
                try {
                    Thread.sleep(RandomObjects.randomWaitTime());
                } catch (Exception e) { 
                    Logger.getLogger(Node.class.getName()).log(Level.SEVERE, null, e);
                }
                speed = RandomObjects.randomSpeed();
                t = RandomObjects.randomWalkTime() * 1000;
                direction = RandomObjects.randomDirection();
            }
            move();
            netControl.addConnetions();
            netControl.updateModel();
            try {
                Thread.sleep(tick);
                t -= tick;
            } catch (InterruptedException ex) {
                Logger.getLogger(Node.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    
    private NetworkController netControl;
    private Color color;
    private Point startPoint;
    private boolean selected;
    private boolean running;
    private int range;
    private int minRange;
    private int speed;
    private int direction;
    private boolean busy;
    private int power;
    private boolean sending;
    private ArrayList<Node> route;
    private int cont;
}
